package org.custom.spring.boot.mybatis.utils;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.session.Configuration;

import static org.custom.spring.boot.mybatis.constant.MybatisConstant.ADDITIONAL;

/**
 * Mybatis-sExecutor接口相关工具类
 *
 * @author 李坤
 */
public final class MybatisExecutorUtil {
    /**
     * 重新设置 MappedStatement 的 BoundSql
     *
     * @param invocation  Invocation
     * @param statement   MappedStatement
     * @param oldBoundSql 被替换的 BoundSql
     * @param finalSql    最终执行的 SQL
     */
    public static void resetParameter2Invocation(Invocation invocation, MappedStatement statement, BoundSql oldBoundSql, String finalSql) {
        // 使用原参数数据重新构建 BoundSql
        BoundSql boundSql = generateBoundSqlByFinalSql(statement.getConfiguration(), oldBoundSql, finalSql);
        // 将最新生成的MappedStatement设置到原方法的参数中
        invocation.getArgs()[0] = generateTargetAndCopyProperties(statement, new BoundSqlSource(boundSql));
    }

    /*--------------------------------------------------private methods-----------------------------------------------*/

    private static BoundSql generateBoundSqlByFinalSql(Configuration configuration, BoundSql boundSql, String finalSql) {
        // 使用原参数数据重新构建 BoundSql
        BoundSql boundSql2New = new BoundSql(configuration, finalSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
        // 将 boundSql.additionalParameters 设置到最新的 boundSql2New 中
        ReflectUtil.setFieldValue(boundSql2New, ADDITIONAL, ReflectUtil.getFieldValue(boundSql, ADDITIONAL));

        return boundSql2New;
    }

    private static MappedStatement generateTargetAndCopyProperties(MappedStatement statement, SqlSource sqlSource) {
        MappedStatement.Builder builder = new MappedStatement.Builder(statement.getConfiguration(),
                statement.getId(), sqlSource, statement.getSqlCommandType());

        builder.cache(statement.getCache());
        builder.timeout(statement.getTimeout());
        builder.useCache(statement.isUseCache());
        builder.resource(statement.getResource());
        builder.fetchSize(statement.getFetchSize());
        builder.resultMaps(statement.getResultMaps());
        builder.parameterMap(statement.getParameterMap());
        builder.keyGenerator(statement.getKeyGenerator());
        builder.statementType(statement.getStatementType());
        builder.resultSetType(statement.getResultSetType());
        builder.flushCacheRequired(statement.isFlushCacheRequired());

        if (ObjectUtil.isNotEmpty(statement.getKeyProperties())) {
            builder.keyProperty(statement.getKeyProperties()[0]);
        }
        return builder.build();
    }

    private static class BoundSqlSource implements SqlSource {
        private final BoundSql boundSql;

        public BoundSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object parameter) {
            return boundSql;
        }
    }
}
